const net = require('net');
var nat = {
    config: {servPort: 8080,natPort: 82,proxy:39002},
    registry: {},//服务注册表
    request: {},//请求连接
    uid(pre = '', howMany = 1, len = 4) {
        if (howMany > 9999) return false;
        var date = new Date();
        var str = date.getFullYear();
        str += ('0' + (date.getMonth() + 1)).slice(-2);
        str += ('0' + date.getDate()).slice(-2);
        str += ('0' + date.getHours()).slice(-2);
        str += ('0' + date.getMinutes()).slice(-2);
        str += ('0' + date.getSeconds()).slice(-2);
        if (!this.num || this.num.time !== str)
            this.num = { time: str, counter: 0 }
        let bd = [];
        for (var i = 0; i < howMany; i++) {
            this.num.counter++;
            var buffNum = (Array(len).join(0) + this.num.counter).slice(len * (-1));
            bd.push(pre + str + buffNum);
        }
        return bd.length > 1 ? bd : bd[0];
    },
    run(){
        this.nat()
        this.proxy()
    },
    /*************** TCP内网穿透 ***************/
    nat() {
        //创建主服务器
        var that = this;
        net.Server(async socket => {
            socket.on('data',data=>{
                console.log(`data: ${data}`);
                socket.removeAllListeners('data');
                try {data=JSON.parse(data)} catch (err) {return false}
                if (data.act == 'regist') {
                    data.name.forEach(item => {
                        that.registry[item] = { client: socket }
                    })
                    socket.NatNames=data.name
                    socket.on('close',had_error=>{
                        socket.NatNames.forEach(item=>{
                            delete that.registry[item]
                        })
                    })
                } else if (data.act == 'connect') {
                    var request=that.request[data.key]
                    if(request&&request.data&&request.socket){
                        request.socket.pipe(socket).pipe(request.socket)
                        socket.write(request.data)
                        request.socket.on('error',err=>{request.socket.end();socket.end()})
                        socket.on('error',err=>{request.socket.end();socket.end()})
                    }
                }
            })
            socket.on('error',(err)=>{socket.end();console.log(`${new Date().toLocaleString()}: ${err.message}`)})
            
        }).listen(that.config.servPort,()=>{console.log(`NAR serv running at ${that.config.servPort}`)});
        //创建代理通信服务器
        net.Server(socket => {
            var key = this.uid('RC')
            that.request[key] = {socket}
            socket.on('data', data => {
                socket.removeAllListeners('data');
                that.request[key].data=data
                // console.log(`data: ${data.toString()}`);
                // var domain = data.toString().match(/Host: (\S)+/i)[0].split(':')[1];
                // var nat = domain.split('.')[0].trim()
                var nat = 'wall';
                console.log(`nat: ${nat}`);
                if (that.registry[nat]){
                    var str=JSON.stringify({ act: 'connect', key, nat})
                    console.log(`str: ${str}`);
                    that.registry[nat].client.write(str)
                }
            })
        }).listen(that.config.natPort,()=>{console.log(`NAR http running at ${that.config.natPort}`)})
    },
    /*************** http[s]代理 ***************/
    proxy(){
        var that=this
        const net = require("net")
        net.Server((client) => {
            client.on("data", (data) => {
                let req = that.reqParse(data)
                if (!req) return;
                let server = net.connect(req.port, req.host);
                client.removeAllListeners('data');
                client.pipe(server).pipe(client);
                server.on('error',err=>{client.end(),server.end()})
                client.on('error',err=>{client.end(),server.end()})
                if (req.method == 'CONNECT')
                    client.write("HTTP/1.1 200 Connection established\r\nConnection: close\r\n\r\n");
                else
                    server.write(req.buffer);
            })
        }).listen(this.config.proxy, () => {console.log("prox running at port: " + this.config.proxy)})
    },
    /*************** 格式化http请求头 ***************/
    reqParse(b) {
        let s = b.toString('utf8');
        let method = s.split('\n')[0].match(/^([A-Z]+)\s/)[1];
        if (method == 'CONNECT') {
            var arr = s.match(/^([A-Z]+)\s([^\:\s]+)\:(\d+)\sHTTP\/(\d\.\d)/);
            if (arr && arr[1] && arr[2] && arr[3] && arr[4])
                return {
                    method: arr[1],
                    host: arr[2],
                    port: arr[3],
                    httpVersion: arr[4],
                    buffer: b
                };
        } else {
            let arr = s.match(/^([A-Z]+)\s([^\s]+)\sHTTP\/(\d\.\d)/);
            let index = 0;
            //请求头，请求体处理
            for (var i = 0, len = b.length - 3; i < len; i++) {
                if (b[i] == 0x0d && b[i + 1] == 0x0a && b[i + 2] == 0x0d && b[i + 3] == 0x0a) {
                    index = i + 4;
                }
            }
            if (!index) return false;
            var header = b.slice(0, index).toString('utf8');
            //替换connection头
            header = header.replace(/(proxy\-)?connection\:.+\r\n/ig, '')
                .replace(/Keep\-Alive\:.+\r\n/i, '')
                .replace("\r\n", '\r\nConnection: close\r\n');
            //替换网址格式(去掉域名部分)
            if (arr[1] == '1.1') {
                var url = arr[2].replace(/http\:\/\/[^\/]+/, '');
                if (arr[2] != url) header = header.replace(arr[2], url);
            }
            b = Buffer.concat([Buffer.from(header), b.slice(index)]);

            if (arr && arr[1] && arr[2] && arr[3]) {
                var host = s.match(/Host\:\s+([^\n\s\r]+)/)[1];
                if (host) {
                    var _p = host.split(':', 2);
                    return {
                        method: arr[1],
                        host: _p[0],
                        port: _p[1] ? _p[1] : 80,
                        path: arr[2],
                        httpVersion: arr[3],
                        buffer: b
                    };
                }
            }
        }
        return false;
    }
}
nat.run()
